home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 0.9.1.3 stable / flock-0.9.1.3.en-US.win32.exe / flock / components / flockFeedManager.js < prev    next >
Text File  |  2007-10-12  |  68KB  |  2,232 lines

  1. //
  2. // BEGIN FLOCK GPL
  3. // 
  4. // Copyright Flock Inc. 2005-2007
  5. // http://flock.com
  6. // 
  7. // This file may be used under the terms of of the
  8. // GNU General Public License Version 2 or later (the "GPL"),
  9. // http://www.gnu.org/licenses/gpl.html
  10. // 
  11. // Software distributed under the License is distributed on an "AS IS" basis,
  12. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13. // for the specific language governing rights and limitations under the
  14. // License.
  15. // 
  16. // END FLOCK GPL
  17. //
  18.  
  19. const FEED_CONTRACTID = '@flock.com/feed;1';
  20. const FEED_CLASSID    = Components.ID('2f7499f4-b47f-4350-a6c5-aee66520c720');
  21. const FEED_CLASSNAME  = 'Flock Feed';
  22.  
  23. const ITEM_CONTRACTID = '@flock.com/feed-item;1';
  24. const ITEM_CLASSID    = Components.ID('f97d9a24-2463-4e8d-b383-9e41f2bc60e8');
  25. const ITEM_CLASSNAME  = 'Flock Feed Item';
  26.  
  27. const FC_CONTRACTID   = '@flock.com/feed-context;1';
  28. const FC_CLASSID      = Components.ID('6e95c222-6707-42cf-aa1b-12baea3983e6');
  29. const FC_CLASSNAME    = 'Flock Feed Context';
  30.  
  31. const FF_CONTRACTID   = '@flock.com/feed-folder;1';
  32. const FF_CLASSID      = Components.ID('16b7ab70-c745-4f53-83a0-3375a031a113');
  33. const FF_CLASSNAME    = 'Flock Feed Folder';
  34.  
  35. const FM_CONTRACTID   = '@flock.com/feed-manager;1';
  36. const FM_CLASSID      = Components.ID('287848be-bbc9-42aa-953e-2ec916eb8d49');
  37. const FM_CLASSNAME    = 'Flock Feed Manager';
  38.  
  39.  
  40. const FEED_CONTENT_FILE      = 'feedcontent.sqlite';
  41.  
  42. const EXCERPT_MAX_WORDS      = 50;
  43. const TITLE_TRIM_MAX_CHARS   = 30;
  44.  
  45. const PURGE_RUN_INTERVAL     = 500;
  46. const PURGE_SLEEP_INTERVAL   = 1500;
  47.  
  48. const UNKNOWN_FEED_TYPE      = 'unknown';
  49.  
  50. const URI_FEED_PROPERTIES    = 'chrome://flock/locale/feeds/feeds.properties';
  51.  
  52. const URN_FEED_ROOT          = 'urn:flock:feedroot';
  53.  
  54. const NEWS_CONTEXT_NAME      = 'news';
  55. const LIVEMARKS_CONTEXT_NAME = 'livemarks';
  56.  
  57. const ATTR_URI_LIST          = ['action', 'href', 'src', 'longdesc', 'usemap',
  58.                                 'cite'];
  59.  
  60.  
  61. /* prefs */
  62. const PREF_EXPIRATION_TIME_BRANCH      = 'flock.feeds.expiration_time';
  63. const PREF_EXPIRATION_TIME             = '.subscriptions';
  64. const PREF_METADATA_EXPIRATION_TIME    = '.metadata_only';
  65.  
  66. const DEFAULT_EXPIRATION_TIME          = 60;
  67. const DEFAULT_METADATA_EXPIRATION_TIME = 24 * 60;
  68.  
  69. const FAVICON_EXPIRATION_TIME          = 24 * 60 * 60 * 1000;
  70.  
  71.  
  72. /* cardinal to danphe migration */
  73. const FLOCK_NS                   = 'http://flock.com/rdf#';
  74.  
  75. const OLD_FEEDS_RDF_FILE         = 'flock_subscriptions.rdf';
  76. const OLD_FEEDS_RDF_FILE_RELIC   = 'flock_subscriptions_old.rdf';
  77. const SUBSCRIPTIONS_RDF_ROOT     = 'urn:flock:feed:subscriptions';
  78.  
  79. const OLD_FLAGGED_RDF_FILE       = 'flock_feeds_flagged.rdf';
  80. const OLD_FLAGGED_RDF_FILE_RELIC = 'flock_feeds_flagged_old.rdf';
  81. const FLAGGED_RDF_ROOT           = 'urn:flock:feed';
  82.  
  83. const OLD_ROOT_RDF_FILE          = 'flock_feeds_root.rdf';
  84. const OLD_DISCOVERY_RDF_FILE     = 'flock_feeds_discovery.rdf';
  85.  
  86. const OLD_FEED_DATA_DIR          = 'feeds';
  87. const OLD_FEED_DATA_FILENAME     = 'feed.rdf';
  88. const OLD_FEED_DATA_RDF_PREFIX   = 'urn:flock:feed:';
  89. const OLD_FEED_DATA_POST_PREFIX  = OLD_FEED_DATA_RDF_PREFIX + 'post:';
  90.  
  91. const FORMAT_CONVERSIONS = { 'Atom 1.0': 'atom',
  92.                              'Atom 0.3': 'atom03',
  93.                              'RSS 2.0' : 'rss2',
  94.                              'RSS 1.0' : 'rss1',
  95.                              'RSS 0.9' : 'rss090',
  96.                              'RSS 0.91': 'rss091',
  97.                              'RSS 0.92': 'rss092',
  98.                              'RSS 0.93': 'rss093',
  99.                              'RSS 0.94': 'rss094',
  100.                            };
  101.  
  102.  
  103. const Cc = Components.classes;
  104. const Ci = Components.interfaces;
  105. const Cr = Components.results;
  106.  
  107. /* from nspr's prio.h */
  108. const PR_RDONLY      = 0x01;
  109. const PR_WRONLY      = 0x02;
  110. const PR_RDWR        = 0x04;
  111. const PR_CREATE_FILE = 0x08;
  112. const PR_APPEND      = 0x10;
  113. const PR_TRUNCATE    = 0x20;
  114. const PR_SYNC        = 0x40;
  115. const PR_EXCL        = 0x80;
  116.  
  117.  
  118. var gIOService     = null;
  119. var gFeedStorage   = null;
  120. var gReadMoreBlurb = null;
  121.  
  122.  
  123. function getObserverService() {
  124.   return Cc['@mozilla.org/observer-service;1']
  125.     .getService(Ci.nsIObserverService);
  126. }
  127.  
  128.  
  129. function excerptText(text, link) {
  130.   var words = text.split(/\s+/);
  131.  
  132.   if (words.length <= EXCERPT_MAX_WORDS)
  133.     return text;
  134.  
  135.   var excerpt = words.slice(0, EXCERPT_MAX_WORDS + 1).join(' ');
  136.  
  137.   if (link)
  138.     excerpt += '… <a href="' + link + '">' + gReadMoreBlurb + '</a>';
  139.  
  140.   return excerpt;
  141. }
  142.  
  143.  
  144. function checkEmpty(value) {
  145.   if (value == null) return null;
  146.   var str = value.toString();
  147.   return str ? str : null;
  148. }
  149.  
  150. function makeURI(uri) {
  151.   try {
  152.     return gIOService.newURI(uri, null, null);
  153.   }
  154.   catch (e) {
  155.     return null;
  156.   }
  157. }
  158.  
  159.  
  160. function getFeedNode(url, coop) {
  161.   var urn = coop.Feed.get_id({ URL: url });
  162.   return coop.get(urn);
  163. }
  164.  
  165.  
  166. function insertFavicon(feedNode) {
  167.   var age = Date.now() - feedNode.lastFaviconFetch.getTime();
  168.   if (age < FAVICON_EXPIRATION_TIME)
  169.     return;
  170.  
  171.   var url = makeURI(feedNode.link);
  172.   if (!url)
  173.     return;
  174.  
  175.   if (!/^https?/.test(url.scheme))
  176.     return;
  177.  
  178.   feedNode.lastFaviconFetch = new Date();
  179.  
  180.   var faviconURL = url.prePath + '/favicon.ico';
  181.  
  182.   var hr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
  183.     .createInstance(Ci.nsIXMLHttpRequest);
  184.  
  185.   hr.onload = function(evt) { 
  186.     if (evt.target.status == 200)
  187.       feedNode.favicon = faviconURL;
  188.   };
  189.  
  190.   hr.detachLoadGroup = true;
  191.   hr.open('HEAD', faviconURL);
  192.   hr.send(null);
  193. }
  194.  
  195. function getContextsForFeed(feedNode, coop) {
  196.   var contexts = [];
  197.   var parents = feedNode.getParents();
  198.   for each (var parent in parents) {
  199.     var node = parent;
  200.     while (!node.isInstanceOf(coop.FeedContext)) {
  201.       var nodeParents = node.getParents();
  202.       if (nodeParents.length)
  203.         node = nodeParents[0];
  204.       else
  205.         break;
  206.     }
  207.     if (node.isInstanceOf(coop.FeedContext))
  208.       contexts.push(node);
  209.   }
  210.   return contexts;
  211. }
  212.  
  213. function contextHasObject(contextName, obj, coop) {
  214.   if (obj.isInstanceOf(coop.FeedContext))
  215.     return obj.name == contextName;
  216.  
  217.   var parents = obj.getParents();
  218.   for each (var parent in parents) {
  219.     var node = parent;
  220.     while (!node.isInstanceOf(coop.FeedContext)) {
  221.       var nodeParents = node.getParents();
  222.       if (nodeParents.length)
  223.         node = nodeParents[0];
  224.       else
  225.         break;
  226.     }
  227.  
  228.     if (node.isInstanceOf(coop.FeedContext) && node.name == contextName)
  229.       return true;
  230.   }
  231.  
  232.   return false;
  233. }
  234.  
  235. function setFeedIndexable(feedNode, indexable) {
  236.   feedNode.isIndexable = indexable;
  237.  
  238.   var items = feedNode.children.enumerate();
  239.   while (items.hasMoreElements()) {
  240.     var item = items.getNext();
  241.     item.isIndexable = indexable;
  242.   }
  243. }
  244.  
  245. function updateNextRefresh(feedNode, coop) {
  246.   var contexts = getContextsForFeed(feedNode, coop);
  247.   if (contexts.length == 0)
  248.     return;
  249.  
  250.   var refreshInterval = contexts[0].refreshInterval;
  251.  
  252.   if (contexts.length > 1) {
  253.     for (var i = 1; i < contexts.length; i++) {
  254.       refreshInterval = Math.min(refreshInterval, contexts[i].refreshInterval);
  255.     }
  256.   }
  257.  
  258.   feedNode.nextRefresh = new Date(Date.now() + refreshInterval * 1000);
  259. }
  260.  
  261. function feedItemSorter(a, b) {
  262.   var res = a.datevalue - b.datevalue;
  263.   if (res == 0)
  264.     return b.indexvalue - a.indexvalue;
  265.   else
  266.     return res;
  267. }
  268.  
  269. function createStatement(dbconn, sql) {
  270.   var stmt = dbconn.createStatement(sql);
  271.   var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"]
  272.     .createInstance(Ci.mozIStorageStatementWrapper);
  273.  
  274.   wrapper.initialize(stmt);
  275.   return wrapper;
  276. }
  277.  
  278. function FeedStorage() {
  279.   var dbfile = Cc['@mozilla.org/file/directory_service;1']
  280.     .getService(Ci.nsIProperties).get('ProfD', Ci.nsIFile);
  281.   dbfile.append(FEED_CONTENT_FILE);
  282.  
  283.   var storageService = Cc['@mozilla.org/storage/service;1']
  284.     .getService(Ci.mozIStorageService);
  285.   this._DBConn = storageService.openDatabase(dbfile);
  286.  
  287.   var schema = 'id STRING PRIMARY KEY, feed STRING, ' +
  288.                'content STRING, excerpt STRING';
  289.  
  290.   try {
  291.     this._DBConn.createTable('feed_content', schema);
  292.   }
  293.   catch (e) { }
  294.  
  295.   this._queryContent = createStatement(this._DBConn,
  296.     'SELECT content FROM feed_content WHERE id = :id');
  297.   this._queryExcerpt = createStatement(this._DBConn,
  298.     'SELECT excerpt FROM feed_content WHERE id = :id');
  299.  
  300.   this._removeItem = createStatement(this._DBConn,
  301.     'DELETE FROM feed_content where id = :id');
  302.   this._insertItem = createStatement(this._DBConn,
  303.     'INSERT INTO feed_content (id, feed, content, excerpt) ' +
  304.     'VALUES (:id, :feed, :content, :excerpt)');
  305.  
  306.   this._removeFeed = createStatement(this._DBConn,
  307.     'DELETE FROM feed_content where feed = :feed');
  308. }
  309.  
  310. FeedStorage.prototype = {
  311.   deleteItem: function FS_deleteItem(id) {
  312.     this._removeItem.params.id = id;
  313.     this._removeItem.step();
  314.     this._removeItem.reset();
  315.   },
  316.   saveItem: function FS_saveItem(id, feed, content, excerpt) {
  317.     this.deleteItem(id);
  318.  
  319.     var pp = this._insertItem.params;
  320.     pp.id = id;
  321.     pp.feed = feed;
  322.     pp.content = content;
  323.     pp.excerpt = excerpt;
  324.     this._insertItem.step();
  325.     this._insertItem.reset();
  326.   },
  327.   deleteFeed: function FS_deleteFeed(feed) {
  328.     this._removeFeed.params.feed = feed;
  329.     this._removeFeed.step();
  330.     this._removeFeed.reset();
  331.   },
  332.   getContent: function FS_getContent(id) {
  333.     return this._getData(id, this._queryContent, 'content');
  334.   },
  335.   getExcerpt: function FS_getExcerpt(id) {
  336.     return this._getData(id, this._queryExcerpt, 'excerpt');
  337.   },
  338.   beginTransaction: function FS_beginTransation() {
  339.     this._DBConn.beginTransaction();
  340.   },
  341.   commitTransaction: function FS_commitTransation() {
  342.     this._DBConn.commitTransaction();
  343.   },
  344.   rollbackTransaction: function FS_rollbackTransation() {
  345.     this._DBConn.rollbackTransaction();
  346.   },
  347.   _getData: function FS__getData(id, stmt, field) {
  348.     stmt.reset();
  349.     stmt.params.id = id;
  350.  
  351.     var data = null;
  352.     if (stmt.step())
  353.       data = stmt.row[field];
  354.     stmt.reset();
  355.     return data;
  356.   }
  357. }
  358.  
  359. function Feed(feedNode, context, coop) {
  360.   this._feedNode = feedNode;
  361.   this._context = context;
  362.   this._coop = coop;
  363. }
  364.  
  365. Feed.prototype = {
  366.   getURL: function FEED_getURL() {
  367.     return makeURI(this._feedNode.URL);
  368.   },
  369.   getFinalURL: function FEED_getFinalURL() {
  370.     return makeURI(this._feedNode.finalURL);
  371.   },
  372.   getLink: function FEED_getLink() {
  373.     return makeURI(this._feedNode.link);
  374.   },
  375.   getType: function FEED_getType() {
  376.     return this._feedNode.format;
  377.   },
  378.   getTitle: function FEED_getTitle() {
  379.     return checkEmpty(this._feedNode.name);
  380.   },
  381.   getSubtitle: function FEED_getSubtitle() {
  382.     return checkEmpty(this._feedNode.subtitle);
  383.   },
  384.   getImage: function FEED_getImage() {
  385.     return makeURI(this._feedNode.image);
  386.   },
  387.   getFavicon: function FEED_getFavicon() {
  388.     return makeURI(this._feedNode.favicon);
  389.   },
  390.   getAuthor: function FEED_getAuthor() {
  391.     return checkEmpty(this._feedNode.author);
  392.   },
  393.   getPubDate: function FEED_getPubDate() {
  394.     return this._feedNode.datevalue;
  395.   },
  396.   getItemCount: function FEED_getItemCount() {
  397.     return this._feedNode.count;
  398.   },
  399.   getItem: function FEED_getItem(index) {
  400.     var items = this.getItems();
  401.     var i = 0;
  402.     while (items.hasMoreElements()) {
  403.       var item = items.getNext();
  404.       if (i == index)
  405.         return item;
  406.       i++;
  407.     }
  408.     return null;
  409.   },
  410.   getItems: function FEED_getItems() {
  411.     var filter = function(feed, item, coop) {
  412.       return new FeedItem(feed, item, coop);
  413.     };
  414.     return this._enumerateItems(filter);
  415.   },
  416.   refresh: function FEED_refresh() {
  417.     this._feedNode.nextRefresh = new Date();
  418.   },
  419.   getContext: function FEED_getContext() {
  420.     return this._context;
  421.   },
  422.   getUnreadCount: function FEED_getUnreadCount() {
  423.     return this._feedNode.unseenItems;
  424.   },
  425.   getUnreadItems: function FEED_getUnreadItems() {
  426.     var filter = function(feed, item, coop) {
  427.       if (item.unseen)
  428.         return new FeedItem(feed, item, coop);
  429.     };
  430.     return this._enumerateItems(filter);
  431.   },
  432.   getFlaggedItems: function FEED_getFlaggedItems() {
  433.     var filter = function(feed, item, coop) {
  434.       if (item.flagged)
  435.         return new FeedItem(feed, item, coop);
  436.     };
  437.     return this._enumerateItems(filter);
  438.   },
  439.   setTitle: function FEED_setTitle(title) {
  440.     this._feedNode.name = title;
  441.   },
  442.   markRead: function FEED_markRead() {
  443.     var items = this._feedNode.children.enumerate();
  444.     while (items.hasMoreElements()) {
  445.       var item = items.getNext();
  446.       item.unseen = false;
  447.     }
  448.     var countsPropagator = Cc['@flock.com/stream-counts-propagator;1']
  449.       .getService(Ci.flockIStreamCountsPropagator);
  450.     countsPropagator.syncCounts(this._feedNode.resource());
  451.   },
  452.   getFolder: function FEED_getFolder() {
  453.     if (this._context) {
  454.       var parents = this._feedNode.getParents();
  455.       if (parents.length == 1) {
  456.         return new FeedFolder(parents[0], this._context, this._coop);
  457.       } else if (parents.length > 1) {
  458.         var id = this._context._contextNode.id();
  459.         for each (var parent in parents) {
  460.           var node = parent;
  461.           while (!node.isInstanceOf(this._coop.FeedContext)) {
  462.             var nodeParents = node.getParents();
  463.             if (nodeParents.length)
  464.               node = nodeParents[0];
  465.             else
  466.               break;
  467.           }
  468.           if (node.isInstanceOf(this._coop.FeedContext) && node.id() == id)
  469.             return new FeedFolder(parent, this._context, this._coop);
  470.         }
  471.       }
  472.     }
  473.  
  474.     return null;
  475.   },
  476.   id: function FEED_id() {
  477.     return this._feedNode.id();
  478.   },
  479.  
  480.   _enumerateItems: function FEED__enumerateItems(filterFunc) {
  481.     var feed = this;
  482.     var coop = this._coop;
  483.     var items = this._feedNode.children.enumerate();
  484.  
  485.     var filteredEnumerator = {
  486.       next: null,
  487.       hasMoreElements: function() {
  488.         while (items.hasMoreElements()) {
  489.           this.next = filterFunc(feed, items.getNext(), coop);
  490.           if (this.next)
  491.             return true;
  492.         }
  493.         return false;
  494.       },
  495.       getNext: function() {
  496.         return this.next;
  497.       },
  498.     }
  499.  
  500.     return filteredEnumerator;
  501.   },
  502.  
  503.   getInterfaces: function FEED_getInterfaces(countRef) {
  504.     var interfaces = [Ci.flockIFeed, Ci.flockIFeedFolderItem,
  505.                       Ci.flockICoopObject, Ci.nsIClassInfo, Ci.nsISupports];
  506.     countRef.value = interfaces.length;
  507.     return interfaces;
  508.   },
  509.   getHelperForLanguage: function FEED_getHelperForLanguage(language) {
  510.     return null;
  511.   },
  512.   contractID: FEED_CONTRACTID,
  513.   classDescription: FEED_CLASSNAME,
  514.   classID: FEED_CLASSID,
  515.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  516.  
  517.   QueryInterface: function FEED_QueryInterface(iid) {
  518.     if (iid.equals(Ci.flockIFeed) ||
  519.         iid.equals(Ci.flockIFeedFolderItem) ||
  520.         iid.equals(Ci.flockICoopObject) ||
  521.         iid.equals(Ci.nsIClassInfo) ||
  522.         iid.equals(Ci.nsISupports))
  523.       return this;
  524.     throw Cr.NS_ERROR_NO_INTERFACE;
  525.   }
  526. }
  527.  
  528.  
  529. function FeedItem(feed, itemNode, coop) {
  530.   this._feed = feed;
  531.   this._itemNode = itemNode;
  532.   this._coop = coop;
  533. }
  534.  
  535. FeedItem.prototype = {
  536.   getItemID: function FI_getItemID() {
  537.     return checkEmpty(this._itemNode.itemid);
  538.   },
  539.   getLink: function FI_getLink() {
  540.     return makeURI(this._itemNode.URL);
  541.   },
  542.   getTitle: function FI_getTitle() {
  543.     return checkEmpty(this._itemNode.name);
  544.   },
  545.   getPubDate: function FI_getPubDate() {
  546.     return this._itemNode.pubdate;
  547.   },
  548.   getCorrectedPubDate: function FI_getCorrectedPubDate() {
  549.     return this._itemNode.datevalue;
  550.   },
  551.   getAuthor: function FI_getAuthor() {
  552.     return checkEmpty(this._itemNode.author);
  553.   },
  554.   getContent: function FI_getContent() {
  555.     return gFeedStorage.getContent(this._itemNode.id());
  556.   },
  557.   getExcerpt: function FI_getExcerpt() {
  558.     return gFeedStorage.getExcerpt(this._itemNode.id());
  559.   },
  560.   getContext: function FI_getContext() {
  561.   },
  562.   isRead: function FI_isRead() {
  563.     return !this._itemNode.unseen;
  564.   },
  565.   isFlagged: function FI_isFlagged() {
  566.     return this._itemNode.flagged;
  567.   },
  568.   setRead: function FI_setRead(read) {
  569.     this._itemNode.unseen = !read;
  570.   },
  571.   setFlagged: function FI_setFlagged(flagged) {
  572.     if (this._itemNode.flagged == flagged)
  573.       return;
  574.  
  575.     this._itemNode.flagged = flagged;
  576.  
  577.     var feedNode = this._feed._feedNode;
  578.     var contexts = getContextsForFeed(feedNode, this._coop);
  579.  
  580.     if (flagged) {
  581.       for each (var context in contexts) {
  582.         context.flaggedItems.addItem(this._itemNode);
  583.       }
  584.     } else {
  585.       for each (var context in contexts) {
  586.         context.flaggedItems.children.remove(this._itemNode);
  587.       }
  588.     }
  589.   },
  590.   getFeed: function FI_getFeed() {
  591.     return this._feed;
  592.   },
  593.  
  594.   getInterfaces: function FI_getInterfaces(countRef) {
  595.     var interfaces = [Ci.flockIFeedItem, Ci.nsIClassInfo, Ci.nsISupports];
  596.     countRef.value = interfaces.length;
  597.     return interfaces;
  598.   },
  599.   getHelperForLanguage: function FI_getHelperForLanguage(language) {
  600.     return null;
  601.   },
  602.   contractID: ITEM_CONTRACTID,
  603.   classDescription: ITEM_CLASSNAME,
  604.   classID: ITEM_CLASSID,
  605.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  606.  
  607.   QueryInterface: function FI_QueryInterface(iid) {
  608.     if (iid.equals(Ci.flockIFeedItem) ||
  609.         iid.equals(Ci.nsIClassInfo) ||
  610.         iid.equals(Ci.nsISupports))
  611.       return this;
  612.     throw Cr.NS_ERROR_NO_INTERFACE;
  613.   }
  614. }
  615.  
  616.  
  617. function FeedFolder(folderNode, context, coop) {
  618.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  619.   this._logger.init('feedmanager');
  620.   this._logger.info('created folder object');
  621.  
  622.   this._folderNode = folderNode;
  623.   this._context = context;
  624.   this._coop = coop;
  625. }
  626.  
  627. FeedFolder.prototype = {
  628.   getTitle: function FF_getTitle() {
  629.     return this._folderNode.name;
  630.   },
  631.   getItemCount: function FF_getItemCount() {
  632.   },
  633.   getItems: function FF_getItems() {
  634.   },
  635.   setTitle: function FF_setTitle(title) {
  636.     this._folderNode.name = title;
  637.   },
  638.   getUnreadCount: function FF_getUnreadCount() {
  639.     return this._folderNode.unseenItems;
  640.   },
  641.   markRead: function FF_markRead() {
  642.     var children = this.getChildren();
  643.     while (children.hasMoreElements()) {
  644.       var child = children.getNext().QueryInterface(Ci.flockIFeedFolderItem);
  645.       child.markRead();
  646.     }
  647.   },
  648.   subscribeURL: function FF_subscribeURL(url, title) {
  649.     var feedObj = { URL: url, serviceId: FM_CONTRACTID };
  650.  
  651.     if (title) {
  652.       title = title.replace(/[\r\n]+/g, ' ');
  653.       this._logger.info('subscribing feed ' + url + ' with title ' + title);
  654.       feedObj.name = title;
  655.     } else {
  656.       this._logger.info('subscribing feed ' + url);
  657.     }
  658.  
  659.     var feedNode = new this._coop.Feed(feedObj);
  660.  
  661.     if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
  662.       setFeedIndexable(feedNode, true);
  663.  
  664.     this._folderNode.children.addOnce(feedNode);
  665.  
  666.     if (feedNode.nextRefresh)
  667.       updateNextRefresh(feedNode, this._coop);    
  668.     else
  669.       feedNode.nextRefresh = new Date();
  670.  
  671.     feedNode.isPollable = true;
  672.  
  673.     var feed = new Feed(feedNode, null, this._coop);
  674.     this._context.notifyOnSubscribe(feed);
  675.  
  676.     return feed;
  677.   },
  678.   subscribeFeed: function FF_subscribeFeed(feed) {
  679.     var feedNode = this._subscriptionNode(feed, 'subscribing');
  680.  
  681.     if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
  682.       setFeedIndexable(feedNode, true);
  683.  
  684.     this._folderNode.children.addOnce(feedNode);
  685.  
  686.     updateNextRefresh(feedNode, this._coop);    
  687.     feedNode.isPollable = true;
  688.  
  689.     this._context.notifyOnSubscribe(feed);
  690.   },
  691.   unsubscribeFeed: function FF_unsubscribeFeed(feed) {
  692.     var feedNode = this._subscriptionNode(feed, 'unsubscribing');
  693.  
  694.     this._folderNode.children.remove(feedNode);
  695.  
  696.     var contexts = getContextsForFeed(feedNode, this._coop);
  697.     if (contexts.length == 0)
  698.       feedNode.isPollable = false;
  699.  
  700.     if (contexts.length == 0 ||
  701.         !contextHasObject(NEWS_CONTEXT_NAME, feedNode, this._coop))
  702.       setFeedIndexable(feedNode, false);
  703.  
  704.     this._context.notifyOnUnsubscribe(feed);
  705.   },
  706.   subscribeFeedWithPosition:
  707.   function FF_subscribeFeedWithPosition(feed, target, orientation) {
  708.     var coop = this._coop;
  709.     var feedNode = this._subscriptionNode(feed, 'subscribing');
  710.  
  711.     var check, value;
  712.     if (target instanceof Ci.flockIFeedFolder) {
  713.       value = target.getTitle();
  714.       check = function(child) {
  715.         return child.isInstanceOf(coop.FeedFolder) && child.name == value;
  716.       }
  717.     } else {
  718.       if (orientation == Ci.flockIFeedFolder.ORIENT_INSIDE)
  719.         throw Cr.NS_ERROR_INVALID_ARG;
  720.  
  721.       value = target.getURL().spec;
  722.       check = function(child) {
  723.         return child.isInstanceOf(coop.Feed) && child.URL == value;
  724.       }
  725.     }
  726.  
  727.     var children = this._folderNode.children.enumerate();
  728.     while (children.hasMoreElements()) {
  729.       var node = children.getNext();
  730.       if (check(node)) {
  731.         switch (orientation) {
  732.           case Ci.flockIFeedFolder.ORIENT_INSIDE:
  733.              node.children.addOnce(feedNode);
  734.              break;
  735.           case Ci.flockIFeedFolder.ORIENT_ABOVE:
  736.              var container = this._folderNode.children;
  737.              container.insertAt(feedNode, container.indexOf(node));
  738.              break;
  739.           case Ci.flockIFeedFolder.ORIENT_BELOW:
  740.              var container = this._folderNode.children;
  741.              container.insertAt(feedNode, container.indexOf(node) + 1);
  742.              break;
  743.           default:
  744.              throw Cr.NS_ERROR_INVALID_ARG;
  745.              break;
  746.         }
  747.  
  748.         updateNextRefresh(feedNode, this._coop);    
  749.         feedNode.isPollable = true;
  750.  
  751.         if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
  752.           setFeedIndexable(feedNode, true);
  753.  
  754.         this._context.notifyOnSubscribe(feed);
  755.         return;
  756.       }
  757.     }
  758.  
  759.     throw Components.Exception("Couldn't find feed");
  760.   },
  761.   addFolder: function FF_addFolder(title) {
  762.     this._logger.info('adding folder ' + title);
  763.     var children = this._folderNode.children.enumerate();
  764.     while (children.hasMoreElements()) {
  765.       var child = children.getNext();
  766.       if (child.name == title) {
  767.         var msg = 'Folder already exists: ' + title;
  768.         this._logger.error(msg);
  769.         throw Components.Exception(msg);
  770.       }
  771.     }
  772.     var folder = new this._coop.FeedFolder({ name: title });
  773.     this._folderNode.children.add(folder);
  774.     return new FeedFolder(folder, this._context, this._coop);
  775.   },
  776.   removeFolder: function FF_removeFolder(folder) {
  777.     var name = folder.getTitle();
  778.     this._logger.info('removing folder ' + name);
  779.     var children = this._folderNode.children.enumerate();
  780.     while (children.hasMoreElements()) {
  781.       var child = children.getNext();
  782.       if (child.name == name && child.isInstanceOf(this._coop.FeedFolder)) {
  783.         var childFolder = new FeedFolder(child, this._context, this._coop);
  784.         childFolder._destroySelf();
  785.         return;
  786.       }
  787.     }
  788.     this._logger.warn('folder ' + name + ' not found');
  789.   },
  790.   getChildFolder: function FF_getChildFolder(title) {
  791.     var children = this._folderNode.children.enumerate();
  792.     while (children.hasMoreElements()) {
  793.       var obj = children.getNext();
  794.       if (obj.isInstanceOf(this._coop.FeedFolder) && obj.name == title)
  795.         return new FeedFolder(obj, this._context, this._coop);
  796.     }
  797.     throw Components.Exception("Folder does not exist: " + title);
  798.   },
  799.   getChildren: function FF_getChildren() {
  800.     var coop = this._coop;
  801.     var context = this._context;
  802.  
  803.     var children = this._folderNode.children.enumerate();
  804.  
  805.     var enumerator = {
  806.       hasMoreElements: function() {
  807.         return children.hasMoreElements();
  808.       },
  809.       getNext: function() {
  810.         var obj = children.getNext();
  811.         if (obj.isInstanceOf(coop.FeedFolder))
  812.           return new FeedFolder(obj, context, coop)
  813.         else
  814.           return new Feed(obj, context, coop)
  815.       }
  816.     };
  817.  
  818.     return enumerator;
  819.   },
  820.   getFolder: function FF_getFolder() {
  821.     if (this._folderNode.isInstanceOf(this._coop.FeedContext))
  822.       return null;
  823.  
  824.     var parents = this._folderNode.getParents();
  825.     if (parents.length > 0)
  826.       return new FeedFolder(parents[0], this._context, this._coop);
  827.  
  828.     return null;
  829.   },
  830.  
  831.   _subscriptionNode: function FF___subscriptionNode(feed, action) {
  832.     var url = feed.getURL().spec;
  833.     this._logger.info(action + ' feed ' + url + ' from folder ' +
  834.                       this.getTitle());
  835.  
  836.     var feedNode = getFeedNode(url, this._coop);
  837.     if (!feedNode)
  838.       throw Cr.NS_ERROR_FAILURE;
  839.  
  840.     return feedNode;
  841.   },
  842.  
  843.   _destroySelf: function FF__destroySelf() {
  844.     var feeds = [];
  845.     var folders = [];
  846.  
  847.     var children = this._folderNode.children.enumerateBackwards();
  848.     while (children.hasMoreElements()) {
  849.       var child = children.getNext();
  850.       if (child.isInstanceOf(this._coop.FeedFolder)) {
  851.         var folder = new FeedFolder(child, this._context, this._coop);
  852.         folders.push(folder);
  853.       } else if (child.isInstanceOf(this._coop.Feed)) {
  854.         var feed = new Feed(child, this._context, this._coop);
  855.         feeds.push(feed);
  856.       }
  857.     }
  858.  
  859.     for each (var folder in folders)
  860.       folder._destroySelf();
  861.  
  862.     for each (var feed in feeds)
  863.       this.unsubscribeFeed(feed);
  864.  
  865.     var name = this._folderNode.name;
  866.     this._folderNode.destroy();
  867.  
  868.     this._logger.info('folder ' + name + ' removed');
  869.   },
  870.  
  871.   getInterfaces: function FF_getInterfaces(countRef) {
  872.     var interfaces = [Ci.flockIFeedFolder, Ci.flockIFeedFolderItem,
  873.                       Ci.nsIClassInfo, Ci.nsISupports];
  874.     countRef.value = interfaces.length;
  875.     return interfaces;
  876.   },
  877.   getHelperForLanguage: function FF_getHelperForLanguage(language) {
  878.     return null;
  879.   },
  880.   contractID: FF_CONTRACTID,
  881.   classDescription: FF_CLASSNAME,
  882.   classID: FF_CLASSID,
  883.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  884.  
  885.   QueryInterface: function FF_QueryInterface(iid) {
  886.     if (iid.equals(Ci.flockIFeedFolder) ||
  887.         iid.equals(Ci.flockIFeedFolderItem) ||
  888.         iid.equals(Ci.nsIClassInfo) ||
  889.         iid.equals(Ci.nsISupports))
  890.       return this;
  891.     throw Cr.NS_ERROR_NO_INTERFACE;
  892.   }
  893. }
  894.  
  895. function FeedContext(contextNode, coop) {
  896.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  897.   this._logger.init('feedmanager');
  898.   this._logger.info('created context for ' + contextNode.name);
  899.  
  900.   this._coop = coop;
  901.   this._contextNode = contextNode;
  902.  
  903.   this._observers = [];
  904. }
  905.  
  906. FeedContext.prototype = {
  907.   getName: function FC_getName() {
  908.     return this._contextNode.name;
  909.   },
  910.  
  911.   getRoot: function FC_getRoot() {
  912.     return new FeedFolder(this._contextNode, this, this._coop);
  913.   },
  914.  
  915.   getSubscription: function FC_getSubscription(url) {
  916.     if (!this.existsSubscription(url))
  917.       return null;
  918.  
  919.     var feedNode = getFeedNode(url.spec, this._coop);
  920.     return new Feed(feedNode, this, this._coop);
  921.   },
  922.   getSubscriptions: function FC_getSubscriptions() {
  923.     var context = this;
  924.     var coop = this._coop;
  925.     var feeds = this._coop.Feed.all();
  926.  
  927.     var filteredEnumerator = {
  928.       next: null,
  929.       hasMoreElements: function() {
  930.         while (feeds.hasMoreElements()) {
  931.           var feedNode = feeds.getNext();
  932.           if (contextHasObject(context._contextNode.name, feedNode, coop)) {
  933.             this.next = new Feed(feedNode, context, coop);
  934.             return true;
  935.           }
  936.         }
  937.         return false;
  938.       },
  939.       getNext: function() {
  940.         return this.next;
  941.       }
  942.     }
  943.    
  944.     return filteredEnumerator; 
  945.   },
  946.   existsSubscription: function FC_existsSubscription(url) {
  947.     var feedNode = getFeedNode(url.spec, this._coop);
  948.     if (!feedNode)
  949.       return false;
  950.  
  951.     return contextHasObject(this._contextNode.name, feedNode, this._coop);
  952.   },
  953.  
  954.   refresh: function FC_refresh() {
  955.     var feeds = this.getSubscriptions();
  956.     while (feeds.hasMoreElements()) {
  957.       var feed = feeds.getNext().QueryInterface(Ci.flockIFeed);
  958.       feed.refresh();
  959.     }
  960.   },
  961.  
  962.   getRefreshInterval: function FC_getRefreshInterval() {
  963.     return this._contextNode.refreshInterval / 60;
  964.   },
  965.   setRefreshInterval: function FC_setRefreshInterval(minutes) {
  966.     this._contextNode.refreshInterval = minutes * 60;
  967.   },
  968.   getFeedItemCap: function FC_getFeedItemCap() {
  969.   },
  970.   setFeedItemCap: function FC_setFeedItemCap(maxItems) {
  971.   },
  972.  
  973.   addObserver: function FC_addObserver(observer) {
  974.     function hasFunc(element, index, array) {
  975.       return element == observer;
  976.     }
  977.     if (!this._observers.some(hasFunc)) {
  978.       this._observers.push(observer);
  979.       if (this._observers.length == 1)
  980.         this._watchUnseenItems();
  981.     }
  982.   },
  983.   removeObserver: function FC_removeObserver(observer) {
  984.     function keepFunc(element, index, array) {
  985.       return element != observer;
  986.     }
  987.     this._observers = this._observers.filter(keepFunc);
  988.     if (this._observers.length == 0)
  989.       this._unwatchUnseenItems();
  990.   },
  991.  
  992.   _watchUnseenItems: function FC__watchUnseenItems() {
  993.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  994.       .getService(Ci.nsIRDFService);
  995.     var unseenItems = RDFS.GetResource('http://flock.com/rdf#unseenItems')
  996.  
  997.     var faves = Cc['@mozilla.org/rdf/datasource;1?name=flock-favorites']
  998.       .getService(Ci.flockIRDFObservable);
  999.     faves.addArcObserver(Ci.flockIRDFObserver.TYPE_CHANGE,
  1000.                          this._contextNode.resource(), unseenItems,
  1001.                          null, this); 
  1002.   },
  1003.   _unwatchUnseenItems: function FC__unwatchUnseenItems() {
  1004.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  1005.       .getService(Ci.nsIRDFService);
  1006.     var unseenItems = RDFS.GetResource('http://flock.com/rdf#unseenItems')
  1007.  
  1008.     var faves = Cc['@mozilla.org/rdf/datasource;1?name=flock-favorites']
  1009.       .getService(Ci.flockIRDFObservable);
  1010.     faves.removeArcObserver(Ci.flockIRDFObserver.TYPE_CHANGE,
  1011.                             this._contextNode.resource(), unseenItems,
  1012.                             null, this); 
  1013.   },
  1014.   rdfChanged: function FC_rdfChanged(ds, type, rsrc, pred, obj, oldObj) {
  1015.     for each (var observer in this._observers) {
  1016.       observer.onUnreadCountChange(this, this._contextNode.unseenItems);
  1017.     }
  1018.   },
  1019.  
  1020.   notifyOnSubscribe: function FC__notifyOnSubscribe(feed) {
  1021.     for each (var observer in this._observers) {
  1022.       observer.onSubscribe(this, feed);
  1023.     }
  1024.   },
  1025.   notifyOnUnsubscribe: function FC__notifyOnUnsubscribe(feed) {
  1026.     for each (var observer in this._observers) {
  1027.       observer.onUnsubscribe(this, feed);
  1028.     }
  1029.   },
  1030.  
  1031.   getInterfaces: function FC_getInterfaces(countRef) {
  1032.     var interfaces = [Ci.flockIFeedContext, Ci.flockIRDFObserver,
  1033.                       Ci.nsIClassInfo, Ci.nsISupports];
  1034.     countRef.value = interfaces.length;
  1035.     return interfaces;
  1036.   },
  1037.   getHelperForLanguage: function FC_getHelperForLanguage(language) {
  1038.     return null;
  1039.   },
  1040.   contractID: FC_CONTRACTID,
  1041.   classDescription: FC_CLASSNAME,
  1042.   classID: FC_CLASSID,
  1043.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  1044.  
  1045.   QueryInterface: function FC_QueryInterface(iid) {
  1046.     if (iid.equals(Ci.flockIFeedContext) ||
  1047.         iid.equals(Ci.flockIRDFObserver) ||
  1048.         iid.equals(Ci.nsIClassInfo) ||
  1049.         iid.equals(Ci.nsISupports))
  1050.       return this;
  1051.     throw Cr.NS_ERROR_NO_INTERFACE;
  1052.   }
  1053. }
  1054.  
  1055.  
  1056. function FeedRequest(fm, url, coop, listener, use_feed_api, metadataOnly) {
  1057.   this._fm = fm;
  1058.   this._url = url;
  1059.   this._coop = coop;
  1060.   this._listener = listener;
  1061.   this._use_feed_api = use_feed_api;
  1062.   this._metadataOnly = metadataOnly;
  1063.  
  1064.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  1065.   this._logger.init('feedmanager');
  1066.  
  1067.   this._channel = gIOService.newChannelFromURI(url);
  1068.  
  1069.   try {
  1070.     this._httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel);
  1071.   }
  1072.   catch (e) {
  1073.     this._httpChannel = null;
  1074.   }
  1075. }
  1076.  
  1077. FeedRequest.prototype = {
  1078.   get: function FR_get() {
  1079.     if (this._httpChannel) {
  1080.       var feedNode = getFeedNode(this._url.spec, this._coop);
  1081.       if (feedNode && feedNode.lastModification)
  1082.         this._httpChannel.setRequestHeader('If-Modified-Since',
  1083.                                            feedNode.lastModification,
  1084.                                            false);
  1085.     }
  1086.  
  1087.     this._channel.asyncOpen(this, null);
  1088.   },
  1089.  
  1090.   onStartRequest: function FR_onStartRequest(request, context) {
  1091.     var channel = request.QueryInterface(Ci.nsIChannel);
  1092.  
  1093.     if (Components.isSuccessCode(request.status))
  1094.       channel.contentType = 'text/xml';
  1095.  
  1096.     this._processor = Cc['@mozilla.org/feed-processor;1']
  1097.       .createInstance(Ci.nsIFeedProcessor);
  1098.     this._processor.listener = this;
  1099.  
  1100.     if (this._metadataOnly) {
  1101.       this._processor.QueryInterface(Ci.flockIFeedProcessor);
  1102.       this._processor.parseFeedMetadataAsync(null, channel.URI);
  1103.     } else {
  1104.       this._processor.parseAsync(null, channel.URI);
  1105.     }
  1106.  
  1107.     this._processor.onStartRequest(request, context);
  1108.   },
  1109.  
  1110.   onStopRequest: function FR_onStopRequest(request, context, status) {
  1111.     if (this._processor) {
  1112.       this._processor.onStopRequest(request, context, status);
  1113.       this._processor = null;
  1114.     }
  1115.   },
  1116.  
  1117.   onDataAvailable: function FR_onDataAvailable(request, context, inputStream,
  1118.                                                sourceOffset, count) {
  1119.     if (this._processor)
  1120.       this._processor.onDataAvailable(request, context, inputStream,
  1121.                                       sourceOffset, count);
  1122.   },
  1123.  
  1124.   handleResult: function FR_handleResult(result) {
  1125.     var failed = true;
  1126.  
  1127.     if (result && result.doc) {
  1128.       var feed = this._fm.storeFeed(this._url, result);
  1129.       if (feed) {
  1130.         failed = false;
  1131.  
  1132.         if (this._httpChannel && !this._metadataOnly)
  1133.           this._saveLastModification(feed._feedNode);
  1134.  
  1135.         this._notifyListener(feed);
  1136.       }
  1137.     } else if (this._httpChannel &&
  1138.                Components.isSuccessCode(this._httpChannel.status) &&
  1139.                this._httpChannel.responseStatus == 304) {
  1140.       this._logger.info('Feed not modified: ' + this._url.spec);
  1141.  
  1142.       var feedNode = getFeedNode(this._url.spec, this._coop);
  1143.       if (feedNode) {
  1144.         updateNextRefresh(feedNode, this._coop);
  1145.  
  1146.         if (feedNode.state != 'failed') {
  1147.           feedNode.lastFetch = new Date();
  1148.  
  1149.           var feed = new Feed(feedNode, null, this._coop);
  1150.           this._notifyListener(feed);
  1151.         }
  1152.       }
  1153.       else if (this._listener)
  1154.         this._listener.onError(null);
  1155.  
  1156.       failed = false;
  1157.     }
  1158.  
  1159.     if (failed) {
  1160.       this._logger.warn('Error processing feed: ' + this._url.spec);
  1161.  
  1162.       if (!this._metadataOnly) {
  1163.         var feedNode = getFeedNode(this._url.spec, this._coop);
  1164.         if (feedNode) {
  1165.           feedNode.state = 'failed';
  1166.           updateNextRefresh(feedNode, this._coop);
  1167.  
  1168.           if (this._httpChannel)
  1169.             this._saveLastModification(feedNode);
  1170.         }
  1171.       }
  1172.  
  1173.       if (this._listener)
  1174.         this._listener.onError(null);
  1175.     }
  1176.  
  1177.     this._processor = null;
  1178.   },
  1179.  
  1180.   _notifyListener: function FR__notifyListener(feed) {
  1181.     if (this._listener) {
  1182.       if (this._use_feed_api)
  1183.         this._listener.onGetFeedComplete(feed);
  1184.       else
  1185.         this._listener.onResult();
  1186.     }
  1187.   },
  1188.   _saveLastModification: function FR__saveLastModification(feedNode) {
  1189.     var lastModification = null;
  1190.     try {
  1191.       lastModification = this._httpChannel.getResponseHeader('Last-Modified');
  1192.     }
  1193.     catch (e) { }
  1194.  
  1195.     if (lastModification)
  1196.       feedNode.lastModification = lastModification;
  1197.   },
  1198.  
  1199.   QueryInterface: function FR_QueryInterface(iid) {
  1200.     if (iid.equals(Ci.nsIFeedResultListener) ||
  1201.         iid.equals(Ci.nsIStreamListener) ||
  1202.         iid.equals(Ci.nsIRequestObserver)||
  1203.         iid.equals(Ci.nsISupports))
  1204.       return this;
  1205.     throw Cr.NS_ERROR_NO_INTERFACE;
  1206.   },
  1207. }
  1208.  
  1209.  
  1210. function FeedManager() {
  1211.   var obs = getObserverService();
  1212.   obs.addObserver(this, 'xpcom-shutdown', false);
  1213.  
  1214.   this._start();
  1215. }
  1216.  
  1217. FeedManager.prototype = {
  1218.   _start: function FM__start() {
  1219.     this._profiler = Cc["@flock.com/profiler;1"].getService(Ci.flockIProfiler);
  1220.     var evtID = this._profiler.profileEventStart("feedmgr-init");
  1221.  
  1222.     this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  1223.     this._logger.init('feedmanager');
  1224.     this._logger.info('starting up...');
  1225.  
  1226.     this._obsService = getObserverService();
  1227.  
  1228.     gIOService = Cc['@mozilla.org/network/io-service;1']
  1229.       .getService(Ci.nsIIOService);
  1230.  
  1231.     var sbs = Cc['@mozilla.org/intl/stringbundle;1']
  1232.       .getService(Ci.nsIStringBundleService);
  1233.     var bundle = sbs.createBundle(URI_FEED_PROPERTIES);
  1234.     gReadMoreBlurb = bundle.GetStringFromName('flock.feed.temp.readmore');
  1235.  
  1236.     this._coop = Cc['@flock.com/singleton;1'].getService(Ci.flockISingleton)
  1237.       .getSingleton('chrome://browser/content/flock/common/load-faves-coop.js')
  1238.       .wrappedJSObject;
  1239.  
  1240.     gFeedStorage = new FeedStorage();
  1241.  
  1242.     var feedroot = new this._coop.Folder(URN_FEED_ROOT);
  1243.  
  1244.     this._feedContexts = {};
  1245.     this.createFeedContext(NEWS_CONTEXT_NAME);
  1246.     this.createFeedContext(LIVEMARKS_CONTEXT_NAME);
  1247.  
  1248.     this._coop.FeedItem.add_destroy_notifier(this._deleteItemContent);
  1249.  
  1250.     var prefService = Cc['@mozilla.org/preferences-service;1']
  1251.       .getService(Ci.nsIPrefBranch2);
  1252.     prefService.addObserver(PREF_EXPIRATION_TIME_BRANCH, this, false);
  1253.  
  1254.     this.observe(null, 'nsPref:changed', PREF_EXPIRATION_TIME);
  1255.     this.observe(null, 'nsPref:changed', PREF_METADATA_EXPIRATION_TIME);
  1256.  
  1257.     this._profiler.profileEventEnd(evtID, "");
  1258.   },
  1259.   _shutdown: function FM__shutdown() {
  1260.     var prefService = Cc['@mozilla.org/preferences-service;1']
  1261.       .getService(Ci.nsIPrefBranch2);
  1262.     prefService.removeObserver(PREF_EXPIRATION_TIME_BRANCH, this);
  1263.  
  1264.     this._coop.FeedItem.remove_destroy_notifier(this._deleteItemContent);
  1265.  
  1266.     gIOService   = null;
  1267.     gFeedStorage = null;
  1268.  
  1269.     this._obsService = null;
  1270.   },
  1271.   _updateExpirationTimes: function FM__updateExpirationTimes(topic, pref) {
  1272.     var prefService = Cc['@mozilla.org/preferences-service;1']
  1273.       .getService(Components.interfaces.nsIPrefService);
  1274.     var prefBranch = prefService.getBranch(PREF_EXPIRATION_TIME_BRANCH);
  1275.  
  1276.     var val = 0;
  1277.     try {
  1278.       val = prefBranch.getIntPref(pref);
  1279.     }
  1280.     catch (e) { }
  1281.  
  1282.     if (pref == PREF_EXPIRATION_TIME) {
  1283.       this._refreshExpire  = val > 0 ? val : DEFAULT_EXPIRATION_TIME;
  1284.       this._refreshExpire *= 60 * 1000;
  1285.     } else {
  1286.       this._metadataExpire = val > 0 ? val : DEFAULT_METADATA_EXPIRATION_TIME;
  1287.       this._metadataExpire *= 60 * 1000;
  1288.     }
  1289.   },
  1290.  
  1291.   observe: function FM_observe(subject, topic, state) {
  1292.     var obs = getObserverService();
  1293.  
  1294.     switch (topic) {
  1295.       case 'xpcom-shutdown':
  1296.         obs.removeObserver(this, 'xpcom-shutdown');
  1297.         this._shutdown();
  1298.         break;
  1299.  
  1300.       case 'nsPref:changed':
  1301.         this._updateExpirationTimes(topic, state);
  1302.         break;
  1303.     }
  1304.   },
  1305.  
  1306.   getFeed: function FM_getFeed(url, listener) {
  1307.     this._logger.info('getting feed: ' + url.spec);
  1308.  
  1309.     var feedNode = getFeedNode(url.spec, this._coop);
  1310.     if (feedNode && !feedNode.metadataOnly) {
  1311.       var age = Date.now() - feedNode.lastFetch.getTime();
  1312.       if (age < this._refreshExpire) {
  1313.         var feed = new Feed(feedNode, null, this._coop);
  1314.         if (listener)
  1315.           listener.onGetFeedComplete(feed);
  1316.         return;
  1317.       }
  1318.     }
  1319.  
  1320.     var req = new FeedRequest(this, url, this._coop, listener, true, false);
  1321.     req.get();
  1322.   },
  1323.  
  1324.   getFeedMetadata: function FM_getFeedMetadata(url, listener) {
  1325.     this._logger.info('getting feed metatdata: ' + url.spec);
  1326.  
  1327.     var feedNode = getFeedNode(url.spec, this._coop);
  1328.     if (feedNode) {
  1329.       if (!feedNode.metadataOnly) {
  1330.         this.getFeed(url, listener);
  1331.         return;
  1332.       } else {
  1333.         var age = Date.now() - feedNode.lastFetch.getTime();
  1334.         if (age < this._metadataExpire) {
  1335.           var feed = new Feed(feedNode, null, this._coop);
  1336.           if (listener)
  1337.             listener.onGetFeedComplete(feed);
  1338.           return;
  1339.         }
  1340.       }
  1341.     }
  1342.  
  1343.     var req = new FeedRequest(this, url, this._coop, listener, true, true);
  1344.     req.get();
  1345.   },
  1346.  
  1347.   createFeedContext: function FM_createFeedContext(name) {
  1348.     var ctxt = this._feedContexts[name];
  1349.     if (ctxt)
  1350.       return ctxt;
  1351.  
  1352.     var context = new this._coop.FeedContext({ name: name });
  1353.     context.flaggedItems = new this._coop.FeedFlaggedStream({ context: context });
  1354.  
  1355.     var feedroot = this._coop.get(URN_FEED_ROOT);
  1356.     feedroot.children.addOnce(context);
  1357.  
  1358.     ctxt = new FeedContext(context, this._coop);
  1359.     this._feedContexts[name] = ctxt;
  1360.  
  1361.     return ctxt;
  1362.   },
  1363.  
  1364.   getFeedContext: function FM_getFeedContext(name) {
  1365.     var ctxt = this._feedContexts[name];
  1366.     if (ctxt)
  1367.       return ctxt;
  1368.  
  1369.     var urn = this._coop.FeedContext.get_id({ name: name });
  1370.     var context = this._coop.get(urn);
  1371.     if (context) {
  1372.       ctxt = new FeedContext(context, this._coop);
  1373.       this._feedContexts[name] = ctxt;
  1374.       return ctxt;
  1375.     } else {
  1376.       throw Components.Exception("No feed context named " + name);
  1377.     }
  1378.   },
  1379.  
  1380.   getFeedContexts: function FM_getFeedContexts() {
  1381.     var contexts = this._coop.FeedContext.all();
  1382.     var fm = this;
  1383.     var ctor = function(contextNode, coop) {
  1384.       var ctxt = fm._feedContexts[contextNode.name];
  1385.       if (!ctxt) {
  1386.         ctxt = new FeedContext(contextNode, coop);
  1387.         fm.feedContexts[contextNode.name] = ctxt;
  1388.       }
  1389.       return ctxt;
  1390.     }
  1391.     return this._enumerateObjects(contexts, ctor);
  1392.   },
  1393.  
  1394.   deleteFeedContext: function FM_deleteFeedContext(name) {
  1395.     var urn = this._coop.FeedContext.get_id({ name: name });
  1396.     var context = this._coop.get(urn);
  1397.     if (!context)
  1398.       throw Components.Exception("No feed context named " + name);
  1399.     
  1400.     delete this._feedContexts[name];
  1401.  
  1402.     context.flaggedItems.destroy();
  1403.     // FIXME: delete folders as well
  1404.     context.destroy();
  1405.   },
  1406.  
  1407.   existsFeed: function FM_existsFeed(url) {
  1408.     var urn = this._coop.Feed.get_id({ URL: url.spec });
  1409.     return this._coop.Feed.exists(urn);
  1410.   },
  1411.  
  1412.   refresh: function FM_refresh(urn, listener) {
  1413.     this._logger.info('refreshing feed: ' + urn);
  1414.     var feedNode = this._coop.get(urn);
  1415.     if (feedNode) {
  1416. /*
  1417.       if (!feedNode.metadataOnly) {
  1418.         var age = Date.now() - feedNode.lastFetch.getTime();
  1419.         if (age < this._refreshExpire) {
  1420.           if (listener)
  1421.             listener.onResult();
  1422.           return;
  1423.         }
  1424.       }
  1425. */
  1426.  
  1427.       var req = new FeedRequest(this, makeURI(feedNode.URL), this._coop,
  1428.                                 listener, false, false);
  1429.       req.get();
  1430.     } else {
  1431.       var msg = 'could not get feed: ' + urn;
  1432.       this._logger.error(msg);
  1433.       throw Components.Exception(msg, Cr.NS_ERROR_UNEXPECTED);
  1434.     }
  1435.   },
  1436.  
  1437.   getFeedFolderItem: function FM_getFeedFolderItem(urn) {
  1438.     this._logger.info('getting feed folder item: ' + urn);
  1439.     var node = this._coop.get(urn);
  1440.     if (node) {
  1441.       var context = this.getFeedContext(NEWS_CONTEXT_NAME);
  1442.       if (node.isInstanceOf(this._coop.Feed)) {
  1443.         return new Feed(node, context, this._coop);
  1444.       } else if (node.isInstanceOf(this._coop.FeedFolder)) {
  1445.         return new FeedFolder(node, context, this._coop);
  1446.       }
  1447.     }
  1448.  
  1449.     this._logger.warn('could not get feed folder item: ' + urn);
  1450.     return null;
  1451.   },
  1452.   getFeedItem: function FM_getFeedItem(urn) {
  1453.     this._logger.info('getting feed item: ' + urn);
  1454.     var node = this._coop.get(urn);
  1455.     if (node && node.isInstanceOf(this._coop.FeedItem)) {
  1456.       var parents = node.getParents();
  1457.       for each (var parent in parents) {
  1458.         if (parent.isInstanceOf(this._coop.Feed)) {
  1459.           var context = this.getFeedContext(NEWS_CONTEXT_NAME);
  1460.           var feed = new Feed(parent, context, this._coop);
  1461.           return new FeedItem(feed, node, this._coop);
  1462.         }
  1463.       }
  1464.     }
  1465.  
  1466.     this._logger.warn('could not get feed item: ' + urn);
  1467.     return null;
  1468.   },
  1469.  
  1470.   getLibrary: function FM_getLibrary() {
  1471.     var feeds = this._coop.Feed.all();
  1472.     var ctor = function(feedNode, coop) {
  1473.       return new Feed(feedNode, null, coop);
  1474.     }
  1475.     return this._enumerateObjects(feed, ctor);
  1476.   },
  1477.  
  1478.   storeFeed: function FM_storeFeed(feedURL, feedResult) {
  1479.     if (feedResult.bozo)
  1480.       return null;
  1481.  
  1482.     this._logger.info('storing feed: ' + feedURL.spec);
  1483.  
  1484.     var feed = feedResult.doc;
  1485.     feed.QueryInterface(Ci.nsIFeed);
  1486.  
  1487.     var title    = feed.title    ? feed.title.plainText()    : null;
  1488.     var subtitle = feed.subtitle ? feed.subtitle.plainText() : null;
  1489.  
  1490.     var format = feedResult.version ? feedResult.version : UNKNOWN_FEED_TYPE;
  1491.  
  1492.     var author = null;
  1493.     if (feed.authors && feed.authors.length)
  1494.       author = feed.authors.queryElementAt(0, Ci.nsIFeedPerson).name;
  1495.  
  1496.     var image = null;
  1497.     try {
  1498.       image = makeURI(feed.image.getPropertyAsAString('url'));
  1499.     }
  1500.     catch (e) { }
  1501.  
  1502.     var existingFeed = getFeedNode(feedURL.spec, this._coop);
  1503.  
  1504.     var flockFeed = feed.QueryInterface(Ci.flockIFeedContainer);
  1505.     if (flockFeed.metadataOnly)
  1506.       this._coop.beginRecoverableUpdates();
  1507.     else if (existingFeed)
  1508.       title = existingFeed.name;
  1509.  
  1510.     var feedNode = this._newFeedNode(feedURL, feedResult.uri,
  1511.                                      title, subtitle, feed.link, author,
  1512.                                      format, image, flockFeed.metadataOnly);
  1513.  
  1514.     feedNode.lastFetch = new Date();
  1515.  
  1516.     if (flockFeed.metadataOnly) {
  1517.       this._coop.endRecoverableUpdates();
  1518.       return new Feed(feedNode, null, this._coop);
  1519.     }
  1520.  
  1521.     updateNextRefresh(feedNode, this._coop);
  1522.  
  1523.     insertFavicon(feedNode);
  1524.  
  1525.     var feedItems = feed.items;
  1526.     var numItems = feedItems.length;
  1527.  
  1528.     var items = [];
  1529.  
  1530.     for (var i = 0; i < numItems; i++) {
  1531.       var item = feedItems.queryElementAt(i, Ci.nsIFeedEntry);
  1532.       var datevalue = item.updated ? new Date(item.updated)
  1533.                                    : feedNode.datevalue;
  1534.       items.push({item: item, datevalue: datevalue, indexvalue: i});
  1535.     }
  1536.  
  1537.     items.sort(feedItemSorter);
  1538.  
  1539.     var refDate;
  1540.     var children = feedNode.children.enumerateBackwards();
  1541.     if (children.hasMoreElements())
  1542.       refDate = children.getNext().datevalue;
  1543.     else
  1544.       refDate = new Date(0);
  1545.  
  1546.     gFeedStorage.beginTransaction();
  1547.  
  1548.     try {
  1549.       var itemsAdded = 0;
  1550.       for each (var i in items) {
  1551.         var node = this._storeFeedEntry(i.item, feedNode, refDate, feed);
  1552.         if (node)
  1553.           itemsAdded++;
  1554.       }
  1555.     }
  1556.     finally {
  1557.       gFeedStorage.commitTransaction();
  1558.     }
  1559.  
  1560.     children = feedNode.children.enumerateBackwards();
  1561.     if (children.hasMoreElements())
  1562.       feedNode.datevalue = children.getNext().datevalue;
  1563.     else
  1564.       feedNode.datevalue = new Date();
  1565.  
  1566.     var feedObj = new Feed(feedNode, null, this._coop);
  1567.  
  1568.     if (itemsAdded && existingFeed)
  1569.       this._obsService.notifyObservers(feedObj, 'new-feed-items', null);
  1570.  
  1571.     return feedObj;
  1572.   },
  1573.  
  1574.   _newFeedNode: function FM__newFeedNode(feedURL, finalURL, title, subtitle,
  1575.                                          link, author, format, image,
  1576.                                          metadataOnly) {
  1577.     var feedInfo = { URL: feedURL.spec,
  1578.                      finalURL: finalURL ? finalURL.spec : feedURL.spec,
  1579.                      name: title ? title : feedURL.spec,
  1580.                      subtitle: subtitle,
  1581.                      link: link ? link.spec : feedURL.spec,
  1582.                      author: author ? author : null,
  1583.                      format: format ? format : UNKNOWN_FEED_TYPE,
  1584.                      image: image ? image.spec : null,
  1585.                      metadataOnly: metadataOnly,
  1586.                      state: 'ok',
  1587.                      serviceId: FM_CONTRACTID
  1588.                    };
  1589.     return new this._coop.Feed(feedInfo);
  1590.   },
  1591.  
  1592.   _storeFeedEntry: function FM__storeFeedEntry(item, feedNode, refDate, feed) {
  1593.     var title = null;
  1594.     if (item.title)
  1595.       title = item.title.plainText();
  1596.  
  1597.     var author = null;
  1598.     if (item.authors && item.authors.length)
  1599.       author = item.authors.queryElementAt(0, Ci.nsIFeedPerson).name;
  1600.  
  1601.     var pubdate = item.published;
  1602.     if (!pubdate)
  1603.       pubdate = item.updated;
  1604.     if (!pubdate)
  1605.       pubdate = feed.updated;
  1606.  
  1607.     var content = null;
  1608.     if (item.content)
  1609.       content = item.content;
  1610.     else if (item.summary)
  1611.       content = item.summary;
  1612.  
  1613.     var excerptSource = null;
  1614.     if (item.summary)
  1615.        excerptSource = item.summary.plainText();
  1616.     else if (item.content)
  1617.        excerptSource = item.content.plainText();
  1618.  
  1619.     return this._storeFeedItem(feedNode, item.id, title,
  1620.                                item.link, author, pubdate, refDate,
  1621.                                content, excerptSource,
  1622.                                false, false);
  1623.   },
  1624.  
  1625.   _storeFeedItem: function FM__storeFeedItem(feedNode, id, title, link, author,
  1626.                                              pubdate, refDate,
  1627.                                              content, excerptSource,
  1628.                                              read, flagged) {
  1629.     if (!id && link)
  1630.       id = link.spec;
  1631.  
  1632.     if (!link)
  1633.       link = makeURI(feedNode.URL);
  1634.  
  1635.     if (!title && excerptSource)
  1636.       title = excerptSource.substr(0, TITLE_TRIM_MAX_CHARS) + '...';
  1637.  
  1638.     if (title)
  1639.       title = title.replace(/[\r\n]+/g, ' ');
  1640.     else
  1641.       title = link.spec;
  1642.  
  1643.     if (pubdate)
  1644.        pubdate = new Date(pubdate);
  1645.  
  1646.     var itemInfo = { itemid: id,
  1647.                      URL: link.spec,
  1648.                      name: title,
  1649.                      author: author,
  1650.                      unseen: !read,
  1651.                      flagged: flagged,
  1652.                      parentfeed: feedNode.URL
  1653.                    };
  1654.  
  1655.     //TODO: we'll want to be more intelligent about this, but the goal should be to only update feed items that need updating
  1656.     var rdfId = this._coop.FeedItem.get_id(itemInfo);
  1657.     if (rdfId && this._coop.FeedItem.exists(rdfId))
  1658.       return null;
  1659.  
  1660.     itemInfo.pubdate = pubdate;
  1661.  
  1662.     if (!pubdate)
  1663.       itemInfo.datevalue = new Date();
  1664.     else if (pubdate.getTime() > Date.now())
  1665.       itemInfo.datevalue = new Date();
  1666.     else if (pubdate < refDate)
  1667.       itemInfo.datevalue = new Date();
  1668.     else
  1669.       itemInfo.datevalue = pubdate;
  1670.  
  1671.     itemInfo.isIndexable = feedNode.isIndexable;
  1672.  
  1673.     this._logger.info('storing feeditem: ' + itemInfo.name);
  1674.  
  1675.     var itemNode = new this._coop.FeedItem(itemInfo);
  1676.  
  1677.     if (feedNode.children.indexOf(itemNode) < 0)
  1678.       feedNode.addItem(itemNode);
  1679.  
  1680.     if (content) {
  1681.       if (typeof(content) != 'string') {
  1682.         var parser = Cc['@mozilla.org/xmlextras/domparser;1']
  1683.           .createInstance(Ci.nsIDOMParser);
  1684.         var doc = parser.parseFromString('<div/>', 'application/xhtml+xml');
  1685.  
  1686.         var docElem = doc.documentElement;
  1687.         var fragment = content.createDocumentFragment(docElem);
  1688.         docElem.appendChild(fragment);
  1689.  
  1690.         this._filterFeedItemContent(doc, content.base);
  1691.  
  1692.         var serializer = Cc['@mozilla.org/xmlextras/xmlserializer;1']
  1693.           .createInstance(Ci.nsIDOMSerializer);
  1694.         var data = serializer.serializeToString(doc);
  1695.       } else {
  1696.         data = content;
  1697.       }
  1698.  
  1699.       var excerpt = excerptText(excerptSource, itemInfo.URL);
  1700.       gFeedStorage.saveItem(itemNode.id(), feedNode.URL, data, excerpt);
  1701.     } else {
  1702.       gFeedStorage.deleteItem(itemNode.id());
  1703.     }
  1704.  
  1705.     return itemNode;
  1706.   },
  1707.   _filterFeedItemContent: function FM__filterFeedItemContent(doc, baseURI) { 
  1708.     if (!baseURI)
  1709.       return;
  1710.  
  1711.     var tw = doc.createTreeWalker(doc.documentElement,
  1712.                                   Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
  1713.                                   null, false);
  1714.     node = tw.nextNode();
  1715.     while (node) {
  1716.       for each (var attrName in ATTR_URI_LIST) {
  1717.         var attr = node.attributes.getNamedItem(attrName);
  1718.         if (attr) {
  1719.           var nval = attr.nodeValue;
  1720.           try {
  1721.             var newURI = gIOService.newURI(nval, null, baseURI);
  1722.             attr.nodeValue = newURI.spec;
  1723.           }
  1724.           catch (e) { }
  1725.         }
  1726.       }
  1727.       node = tw.nextNode();
  1728.     }
  1729.   },
  1730.  
  1731.   _deleteItemContent: function FM__deleteItemContent(item, coop) {
  1732.     gFeedStorage.deleteItem(item.id());
  1733.   },
  1734.  
  1735.   _purgeFeed: function FM__purgeFeed(feed) {
  1736.     this._logger.info('Purging ' + feed.id());
  1737.  
  1738.     var items = feed.children.enumerateBackwards();
  1739.     if (items.hasMoreElements()) {
  1740.       this._coop.FeedItem.remove_destroy_notifier(this._deleteItemContent);
  1741.  
  1742.       gFeedStorage.deleteFeed(feed.URL);
  1743.  
  1744.       while (items.hasMoreElements())
  1745.         items.getNext().destroy();
  1746.  
  1747.       this._coop.FeedItem.add_destroy_notifier(this._deleteItemContent);
  1748.     }
  1749.  
  1750.     feed.destroy();
  1751.   },
  1752.  
  1753.   _enumerateObjects: function FM__enumerateObjects(objects, ctor) {
  1754.     var coop = this._coop;
  1755.  
  1756.     var enumerator = {
  1757.       hasMoreElements: function() {
  1758.         return objects.hasMoreElements();
  1759.       },
  1760.       getNext: function() {
  1761.         return ctor(objects.getNext(), coop);
  1762.       }
  1763.     }
  1764.  
  1765.     return enumerator;
  1766.   },
  1767.  
  1768.   _purgeOrphanedFeeds: function FM__purgeOrphanedFeeds() {
  1769.     var feeds = this._coop.Feed.all();
  1770.  
  1771.     var fm = this;
  1772.  
  1773.     var purge = {
  1774.       notify: function(timer) {
  1775.         var start = Date.now();
  1776.         while (feeds.hasMoreElements()) {
  1777.           var feed = feeds.getNext();
  1778.  
  1779.           var contexts = getContextsForFeed(feed, fm._coop);
  1780.           if (contexts.length == 0)
  1781.             fm._purgeFeed(feed);
  1782.  
  1783.           if (Date.now() > start + PURGE_RUN_INTERVAL)
  1784.             return;
  1785.         }
  1786.  
  1787.         timer.cancel();
  1788.       }
  1789.     };
  1790.  
  1791.     var timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
  1792.     timer.initWithCallback(purge, PURGE_SLEEP_INTERVAL,
  1793.                            Ci.nsITimer.TYPE_REPEATING_SLACK);
  1794.  
  1795.   },
  1796.  
  1797.   /* flockIHousekeeping interface */
  1798.   runHousekeeping: function FM_runHousekeeping() {
  1799.     this._logger.info('running housekeeping...');
  1800.     this._purgeOrphanedFeeds();
  1801.   },
  1802.  
  1803.   /* flockIMigratable interface */
  1804.   get shortname() { return 'News Feeds'; },
  1805.  
  1806.   needsMigration: function FM_needsMigration(oldVersion) {
  1807.     var oldFeedsFile = this._getOldFeedServiceFile(OLD_FEEDS_RDF_FILE);
  1808.     return oldFeedsFile.exists();
  1809.   },
  1810.   startMigration: function FM_startMigration(oldVersion, listener) {
  1811.     var obs = getObserverService();
  1812.     obs.notifyObservers(null, 'myworld-enableDisable-observers', false);
  1813.     
  1814.     this._start();
  1815.  
  1816.     gFeedStorage.beginTransaction();
  1817.  
  1818.     var ctxt = {
  1819.       listener: listener,
  1820.  
  1821.       oldFeedsFile     : this._getOldFeedServiceFile(OLD_FEEDS_RDF_FILE),
  1822.       oldFlaggedFile   : this._getOldFeedServiceFile(OLD_FLAGGED_RDF_FILE),
  1823.       oldRootFile      : this._getOldFeedServiceFile(OLD_ROOT_RDF_FILE),
  1824.       oldDiscoveryFile : this._getOldFeedServiceFile(OLD_DISCOVERY_RDF_FILE),
  1825.       oldFeedDir       : this._getOldFeedServiceFile(OLD_FEED_DATA_DIR),
  1826.  
  1827.       oldFeedsReported   : false,
  1828.       oldFlaggedReported : false,
  1829.       cleanupReported    : false,
  1830.     };
  1831.  
  1832.     return { wrappedJSObject: ctxt };
  1833.   },
  1834.   finishMigration: function FM_finishMigration(ctxtWrapper) {
  1835.     gFeedStorage.commitTransaction();
  1836.     
  1837.     var obs = getObserverService();
  1838.     obs.notifyObservers(null, 'myworld-enableDisable-observers', true);
  1839.   },
  1840.   doMigrationWork: function FM_doMigrationWork(ctxtWrapper) {
  1841.     var ctxt = ctxtWrapper.wrappedJSObject;
  1842.  
  1843.     if (!ctxt.oldFeedsReported) {
  1844.       ctxt.listener.onUpdate(0, 'Migrating subscriptions');
  1845.       ctxt.oldFeedsReported = true;
  1846.     } else if (ctxt.oldFeedsFile.exists()) {
  1847.       if (!ctxt.oldFeedsMigrator)
  1848.         ctxt.oldFeedsMigrator = this._migrateOldFeeds(ctxt);
  1849.       if (ctxt.oldFeedsMigrator.next())
  1850.         ctxt.oldFeedsMigrator = null;
  1851.     } else if (!ctxt.oldFlaggedReported) {
  1852.       ctxt.listener.onUpdate(-1, 'Migrating saved articles');
  1853.       ctxt.oldFlaggedReported = true;
  1854.     } else if (ctxt.oldFlaggedFile.exists()) {
  1855.       this._migrateOldFlagged(ctxt);
  1856.     } else if (!ctxt.cleanupReported) {
  1857.       ctxt.listener.onUpdate(-1, 'Cleaning up old data');
  1858.       ctxt.cleanupReported = true;
  1859.     } else if (ctxt.oldRootFile.exists()) {
  1860.       ctxt.oldRootFile.remove(false);
  1861.     } else if (ctxt.oldDiscoveryFile.exists()) {
  1862.       ctxt.oldDiscoveryFile.remove(false);
  1863.     } else if (ctxt.oldFeedDir.exists()) {
  1864.       ctxt.oldFeedDir.remove(true);
  1865.       return false;
  1866.     } else {
  1867.       return false;
  1868.     }
  1869.  
  1870.     return true;
  1871.   },
  1872.  
  1873.   _getOldFeedServiceFile: function FM__getOldFeedServiceFile(filename) {
  1874.     var oldFile = Cc['@mozilla.org/file/directory_service;1']
  1875.       .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
  1876.     oldFile.append(filename)
  1877.     return oldFile;
  1878.   },
  1879.   _migrateOldFeeds: function FM__migrateOldFeeds(ctxt) {
  1880.     this._hex_md5 = loadSubScript('chrome://browser/content/flock/common/md5.js').hex_md5;
  1881.  
  1882.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  1883.       .getService(Ci.nsIRDFService);
  1884.     var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
  1885.       .getService(Ci.nsIRDFContainerUtils);
  1886.  
  1887.     var spec = gIOService.newFileURI(ctxt.oldFeedsFile).spec;
  1888.     var ds = RDFS.GetDataSourceBlocking(spec);
  1889.     var root = RDFS.GetResource(SUBSCRIPTIONS_RDF_ROOT);
  1890.  
  1891.     var context = this.getFeedContext(NEWS_CONTEXT_NAME);
  1892.     var news_root = context.getRoot();
  1893.  
  1894.     var titleProp = RDFS.GetResource(FLOCK_NS + 'title');
  1895.  
  1896.     var typeProp = RDFS.GetResource(FLOCK_NS + 'type');
  1897.     var feeds = ds.GetSources(typeProp, RDFS.GetLiteral('feed'), true);
  1898.  
  1899.     var numFeeds = 0;
  1900.     while (feeds && feeds.hasMoreElements()) {
  1901.       var feed = feeds.getNext();
  1902.       numFeeds++;
  1903.     }
  1904.  
  1905.     var children = RDFCU.MakeSeq(ds, root).GetElements(), i = 0;
  1906.     while (children && children.hasMoreElements()) {
  1907.       var child = children.getNext();
  1908.       child.QueryInterface(Ci.nsIRDFResource);
  1909.  
  1910.       if (RDFCU.IsContainer(ds, child)) {
  1911.         var title = ds.GetTarget(child, titleProp, true);
  1912.         if (!title)
  1913.           continue;
  1914.  
  1915.         title.QueryInterface(Ci.nsIRDFLiteral);
  1916.         if (!title.Value)
  1917.           continue;
  1918.  
  1919.         var subFolder = news_root.addFolder(title.Value);
  1920.  
  1921.         var subChildren = RDFCU.MakeSeq(ds, child).GetElements();
  1922.         while (subChildren && subChildren.hasMoreElements()) {
  1923.           var subChild = subChildren.getNext();
  1924.           subChild.QueryInterface(Ci.nsIRDFResource);
  1925.  
  1926.           var url = subChild.Value;
  1927.           var percent = Math.round(i / numFeeds * 100);
  1928.           ctxt.listener.onUpdate(percent, 'Migrating ' + url);
  1929.           yield false;
  1930.  
  1931.           i++;
  1932.           this._migrateFeed(url, subFolder);
  1933.         }
  1934.       } else {
  1935.         var url = child.Value;
  1936.         var percent = Math.round(i / numFeeds * 100);
  1937.         ctxt.listener.onUpdate(percent, 'Migrating ' + url);
  1938.         yield false;
  1939.  
  1940.         i++;
  1941.         this._migrateFeed(url, news_root);
  1942.       }
  1943.     }
  1944.  
  1945.     var oldFile = ctxt.oldFeedsFile.clone();
  1946.     oldFile.moveTo(null, OLD_FEEDS_RDF_FILE_RELIC);
  1947.     //ctxt.oldFeedsFile.remove(false);
  1948.  
  1949.     yield true;
  1950.   },
  1951.   _migrateFeed: function FM__migrateFeed(url, folder) {
  1952.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  1953.       .getService(Ci.nsIRDFService);
  1954.     var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
  1955.       .getService(Ci.nsIRDFContainerUtils);
  1956.  
  1957.     var oldFeedFile = this._getOldFeedServiceFile(OLD_FEED_DATA_DIR);
  1958.     oldFeedFile.append(this._hex_md5(url));
  1959.     oldFeedFile.append(OLD_FEED_DATA_FILENAME);
  1960.  
  1961.     if (!oldFeedFile.exists())
  1962.       return;
  1963.  
  1964.     var spec = gIOService.newFileURI(oldFeedFile).spec;
  1965.     var ds = RDFS.GetDataSourceBlocking(spec);
  1966.     var root = RDFS.GetResource(OLD_FEED_DATA_RDF_PREFIX + url);
  1967.  
  1968.     function getTarget(prop) {
  1969.       var t = ds.GetTarget(root, prop, true);
  1970.       return t ? t.QueryInterface(Ci.nsIRDFLiteral).Value : null;
  1971.     }
  1972.  
  1973.     var title    = getTarget(RDFS.GetResource(FLOCK_NS + 'title'));
  1974.     var subtitle = getTarget(RDFS.GetResource(FLOCK_NS + 'description'));
  1975.     var author   = getTarget(RDFS.GetResource(FLOCK_NS + 'author'));
  1976.     var link     = getTarget(RDFS.GetResource(FLOCK_NS + 'link'));
  1977.     var format   = getTarget(RDFS.GetResource(FLOCK_NS + 'feedtype'));
  1978.     var image    = getTarget(RDFS.GetResource(FLOCK_NS + 'image'));
  1979.     var favicon  = getTarget(RDFS.GetResource(FLOCK_NS + 'favicon'));
  1980.  
  1981.     if (FORMAT_CONVERSIONS[format])
  1982.       format = FORMAT_CONVERSIONS[format];
  1983.     else if (format.indexOf('RSS') == 0)
  1984.       format = 'rssUnknown';
  1985.     else
  1986.       format = null;
  1987.  
  1988.     var feedURL = makeURI(url);
  1989.     if (!feedURL)
  1990.       return;
  1991.  
  1992.     var feedNode = this._newFeedNode(feedURL, null, title, subtitle,
  1993.                                      makeURI(link), author, format,
  1994.                                      makeURI(image), false);
  1995.  
  1996.     var faviconURL = makeURI(favicon);
  1997.     if (faviconURL)
  1998.       feedNode.favicon = faviconURL.spec;
  1999.  
  2000.     folder.subscribeFeed(new Feed(feedNode, null, this._coop));
  2001.  
  2002.     var items = [];
  2003.  
  2004.     var children = RDFCU.MakeSeq(ds, root).GetElements();
  2005.     while (children && children.hasMoreElements()) {
  2006.       var item = this._migrateFeedItem(ds, children.getNext(), false);
  2007.       if (item)
  2008.         items.push(item);
  2009.     }
  2010.  
  2011.     items.sort(feedItemSorter);
  2012.  
  2013.     for each (var i in items) {
  2014.       var args = i.item;
  2015.       args.unshift(feedNode);
  2016.       this._storeFeedItem.apply(this, args);
  2017.     }
  2018.  
  2019.     children = feedNode.children.enumerateBackwards();
  2020.     if (children.hasMoreElements())
  2021.       feedNode.datevalue = children.getNext().datevalue;
  2022.     else
  2023.       feedNode.datevalue = new Date();
  2024.   },
  2025.   _migrateOldFlagged: function FM__migrateOldFlagged(ctxt) {
  2026.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  2027.       .getService(Ci.nsIRDFService);
  2028.     var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
  2029.       .getService(Ci.nsIRDFContainerUtils);
  2030.  
  2031.     var spec = gIOService.newFileURI(ctxt.oldFlaggedFile).spec;
  2032.     var ds = RDFS.GetDataSourceBlocking(spec);
  2033.     var root = RDFS.GetResource(FLAGGED_RDF_ROOT);
  2034.  
  2035.     var context = new this._coop.FeedContext({ name: NEWS_CONTEXT_NAME });
  2036.     var flaggedItems = context.flaggedItems;
  2037.  
  2038.     var feedProp = RDFS.GetResource(FLOCK_NS + 'feed');
  2039.  
  2040.     var children = RDFCU.MakeSeq(ds, root).GetElements();
  2041.     while (children && children.hasMoreElements()) {
  2042.       var child = children.getNext();
  2043.       child.QueryInterface(Ci.nsIRDFResource);
  2044.  
  2045.       var feedURL = ds.GetTarget(child, feedProp, true);
  2046.       if (!feedURL)
  2047.         continue;
  2048.  
  2049.       feedURL.QueryInterface(Ci.nsIRDFLiteral);
  2050.  
  2051.       var feedNode = getFeedNode(feedURL.Value, this._coop);
  2052.       if (feedNode) {
  2053.         var args = this._migrateFeedItem(ds, child, true).item;
  2054.         args.unshift(feedNode);
  2055.         var node = this._storeFeedItem.apply(this, args);
  2056.         if (node)
  2057.           flaggedItems.addItem(node);
  2058.       }
  2059.     }
  2060.  
  2061.     var oldFile = ctxt.oldFlaggedFile.clone();
  2062.     oldFile.moveTo(null, OLD_FLAGGED_RDF_FILE_RELIC);
  2063.     //ctxt.oldFlaggedFile.remove(false);
  2064.   },
  2065.   _migrateFeedItem: function FM__migrateFeedItem(ds, res, doFlagged) {
  2066.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  2067.       .getService(Ci.nsIRDFService);
  2068.  
  2069.     res.QueryInterface(Ci.nsIRDFResource);
  2070.  
  2071.     function getTarget(prop) {
  2072.       var t = ds.GetTarget(res, prop, true);
  2073.       return t ? t.QueryInterface(Ci.nsIRDFLiteral).Value : null;
  2074.     }
  2075.  
  2076.     var flagged = getTarget(RDFS.GetResource(FLOCK_NS + 'flagged'));
  2077.     if (flagged == 'true' && !doFlagged)
  2078.       return;
  2079.  
  2080.     var id = res.Value;
  2081.     if (id.indexOf(OLD_FEED_DATA_POST_PREFIX) == 0)
  2082.       id = id.substr(OLD_FEED_DATA_POST_PREFIX.length);
  2083.     else
  2084.       id = null;
  2085.  
  2086.     var title      = getTarget(RDFS.GetResource(FLOCK_NS + 'title'));
  2087.     var link       = getTarget(RDFS.GetResource(FLOCK_NS + 'link'));
  2088.     var datevalue  = getTarget(RDFS.GetResource(FLOCK_NS + 'datevalue'));
  2089.     var author     = getTarget(RDFS.GetResource(FLOCK_NS + 'author'));
  2090.     var content    = getTarget(RDFS.GetResource(FLOCK_NS + 'description'));
  2091.     var read       = getTarget(RDFS.GetResource(FLOCK_NS + 'read'));
  2092.  
  2093.     datevalue = new Date(Number(datevalue));
  2094.     refDate = new Date(0);
  2095.  
  2096.     var item = [id, title, makeURI(link), author, datevalue, refDate, content,
  2097.                 content, read == 'true', doFlagged];
  2098.  
  2099.     return ({ item: item, datevalue: datevalue, indexvalue: 0 });
  2100.   },
  2101.  
  2102.   getInterfaces: function FM_getInterfaces(countRef) {
  2103.     var interfaces = [Ci.flockIFeedManager, Ci.flockIPollingService,
  2104.                       Ci.flockIMigratable, Ci.flockIHousekeeping,
  2105.                       Ci.nsIObserver, Ci.nsIClassInfo, Ci.nsISupports];
  2106.     countRef.value = interfaces.length;
  2107.     return interfaces;
  2108.   },
  2109.   getHelperForLanguage: function FM_getHelperForLanguage(language) {
  2110.     return null;
  2111.   },
  2112.   contractID: FM_CONTRACTID,
  2113.   classDescription: FM_CLASSNAME,
  2114.   classID: FM_CLASSID,
  2115.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  2116.   flags: Ci.nsIClassInfo.SINGLETON,
  2117.  
  2118.   QueryInterface: function FM_QueryInterface(iid) {
  2119.     if (iid.equals(Ci.flockIFeedManager) ||
  2120.         iid.equals(Ci.flockIPollingService) ||
  2121.         iid.equals(Ci.flockIMigratable) ||
  2122.         iid.equals(Ci.flockIHousekeeping) ||
  2123.         iid.equals(Ci.nsIObserver) ||
  2124.         iid.equals(Ci.nsIClassInfo) ||
  2125.         iid.equals(Ci.nsISupports))
  2126.       return this;
  2127.     throw Cr.NS_ERROR_NO_INTERFACE;
  2128.   }
  2129. }
  2130.  
  2131.  
  2132. function GenericComponentFactory(ctor) {
  2133.   this._ctor = ctor;
  2134. }
  2135.  
  2136. GenericComponentFactory.prototype = {
  2137.  
  2138.   _ctor: null,
  2139.  
  2140.   // nsIFactory
  2141.   createInstance: function(outer, iid) {
  2142.     if (outer != null)
  2143.       throw Cr.NS_ERROR_NO_AGGREGATION;
  2144.     return (new this._ctor()).QueryInterface(iid);
  2145.   },
  2146.  
  2147.   // nsISupports
  2148.   QueryInterface: function(iid) {
  2149.     if (iid.equals(Ci.nsIFactory) ||
  2150.         iid.equals(Ci.nsISupports))
  2151.       return this;
  2152.     throw Cr.NS_ERROR_NO_INTERFACE;
  2153.   },
  2154. };
  2155.  
  2156. var Module = {
  2157.   QueryInterface: function(iid) {
  2158.     if (iid.equals(Ci.nsIModule) ||
  2159.         iid.equals(Ci.nsISupports))
  2160.       return this;
  2161.  
  2162.     throw Cr.NS_ERROR_NO_INTERFACE;
  2163.   },
  2164.  
  2165.   getClassObject: function(cm, cid, iid) {
  2166.     if (!iid.equals(Ci.nsIFactory))
  2167.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  2168.  
  2169.     if (cid.equals(FEED_CLASSID))
  2170.       return new GenericComponentFactory(Feed);
  2171.     if (cid.equals(ITEM_CLASSID))
  2172.       return new GenericComponentFactory(FeedItem);
  2173.     if (cid.equals(FC_CLASSID))
  2174.       return new GenericComponentFactory(FeedContext);
  2175.     if (cid.equals(FF_CLASSID))
  2176.       return new GenericComponentFactory(FeedFolder);
  2177.     if (cid.equals(FM_CLASSID))
  2178.       return new GenericComponentFactory(FeedManager);
  2179.  
  2180.     throw Cr.NS_ERROR_NO_INTERFACE;
  2181.   },
  2182.  
  2183.   registerSelf: function(cm, file, location, type) {
  2184.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  2185.     cr.registerFactoryLocation(FEED_CLASSID, FEED_CLASSNAME, FEED_CONTRACTID,
  2186.                                file, location, type);
  2187.     cr.registerFactoryLocation(ITEM_CLASSID, ITEM_CLASSNAME, ITEM_CONTRACTID,
  2188.                                file, location, type);
  2189.     cr.registerFactoryLocation(FC_CLASSID, FC_CLASSNAME, FC_CONTRACTID,
  2190.                                file, location, type);
  2191.     cr.registerFactoryLocation(FF_CLASSID, FF_CLASSNAME, FF_CONTRACTID,
  2192.                                file, location, type);
  2193.     cr.registerFactoryLocation(FM_CLASSID, FM_CLASSNAME, FM_CONTRACTID,
  2194.                                file, location, type);
  2195.  
  2196.     var catman = Cc['@mozilla.org/categorymanager;1']
  2197.       .getService(Ci.nsICategoryManager);
  2198.  
  2199.     catman.addCategoryEntry('flockMigratable', FM_CLASSNAME, FM_CONTRACTID,
  2200.                             true, true);
  2201.     catman.addCategoryEntry('flockHousekeeping', FM_CLASSNAME, FM_CONTRACTID,
  2202.                             true, true);
  2203.   },
  2204.  
  2205.   unregisterSelf: function(cm, location, type) {
  2206.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  2207.     cr.unregisterFactoryLocation(FEED_CLASSID, location);
  2208.     cr.unregisterFactoryLocation(ITEM_CLASSID, location);
  2209.     cr.unregisterFactoryLocation(FC_CLASSID, location);
  2210.     cr.unregisterFactoryLocation(FF_CLASSID, location);
  2211.     cr.unregisterFactoryLocation(FM_CLASSID, location);
  2212.   },
  2213.  
  2214.   canUnload: function(cm) {
  2215.     return true;
  2216.   },
  2217. };
  2218.  
  2219. function NSGetModule(compMgr, fileSpec)
  2220. {
  2221.   return Module;
  2222. }
  2223.  
  2224.  
  2225. function loadSubScript(spec) {
  2226.   var loader = Cc['@mozilla.org/moz/jssubscript-loader;1']
  2227.     .getService(Ci.mozIJSSubScriptLoader);
  2228.   var context = {};
  2229.   loader.loadSubScript(spec, context);
  2230.   return context;
  2231. }
  2232.